Micron Document
๐ŸŽ–๏ธGitะฏั€ะฐ๐ŸŽ–๏ธ

Node / meshtastic / Meshtastic-Android / files / feature / firmware / src / commonTest / kotlin / org / meshtastic / feature / firmware / CommonFirmwareRetrieverTest.kt

Displaying Raw โ€ข Download

feature/firmware/src/commonTest/kotlin/org/meshtastic/feature/firmware/CommonFirmwareRetrieverTest.kt 9fa9f1e341ee8d2c8fd9fabf9253b2f012fc8c95 (9fa9f1e3) Text, 24.64 KB

T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tf0883e@fileTb4b4b4:Te6edf3SuppressTb4b4b4(Ta5d6ff"Ta5d6ffMagicNumberTa5d6ff"Tb4b4b4)

Tff7b72package T7ee787org.meshtastic.feature.firmware

Tff7b72import T7ee787kotlinx.coroutines.test.runTest
Tff7b72import T7ee787org.meshtastic.core.common.util.CommonUri
Tff7b72import T7ee787org.meshtastic.core.database.entity.FirmwareRelease
Tff7b72import T7ee787org.meshtastic.core.database.entity.FirmwareReleaseType
Tff7b72import T7ee787org.meshtastic.core.model.DeviceHardware
Tff7b72import T7ee787org.meshtastic.feature.firmware.ota.FirmwareHashUtil
Tff7b72import T7ee787kotlin.test.Test
Tff7b72import T7ee787kotlin.test.assertEquals
Tff7b72import T7ee787kotlin.test.assertNotNull
Tff7b72import T7ee787kotlin.test.assertNull
Tff7b72import T7ee787kotlin.test.assertTrue

T8b949e/**
* Tests for [FirmwareRetriever] covering the manifest-first ESP32 firmware resolution strategy and fallback heuristics.
* Uses [FakeFirmwareFileHandler] instead of MockK for KMP compatibility.
*
* This class is `abstract` because the Android `actual` of [CommonUri.parse] delegates to `android.net.Uri.parse()`,
* which requires Robolectric on the Android host-test target. Platform-specific subclasses in `androidHostTest` and
* `jvmTest` apply the necessary runner configuration.
*/
Tff7b72abstract Tff7b72class T56d364CommonFirmwareRetrieverTest Tb4b4b4{

Tff7b72protected Tff7b72companion Tff7b72object Tb4b4b4{
Tff7b72const Tff7b72val Te6edf3BASE_URL Tff7b72= Ta5d6ff"Ta5d6ffhttps://raw.githubusercontent.com/meshtastic/meshtastic.github.io/masterTa5d6ff"

Tff7b72val Te6edf3TEST_RELEASE Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.7.17Ta5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ffhttps://example.com/esp32-s3.zipTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3TEST_HARDWARE Tff7b72=
Te6edf3DeviceHardwareTb4b4b4(Te6edf3hwModelSlug Tff7b72= Ta5d6ff"Ta5d6ffHELTEC_V3Ta5d6ff"Tb4b4b4, Te6edf3platformioTarget Tff7b72= Ta5d6ff"Ta5d6ffheltec-v3Ta5d6ff"Tb4b4b4, Te6edf3architecture Tff7b72= Ta5d6ff"Ta5d6ffesp32-s3Ta5d6ff"Tb4b4b4)

T8b949e/** A valid .mt.json manifest with an app0 entry. */
Tff7b72val Te6edf3MANIFEST_JSON Tff7b72=
Ta5d6ff"""
Ta5d6ff {
Ta5d6ff"Ta5d6fffilesTa5d6ff"Ta5d6ff: [
{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Ta5d6ff,
Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6ffabc123Ta5d6ff"Ta5d6ff,
Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff: 2097152,
Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6ffapp0Ta5d6ff"
Ta5d6ff },
{
Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.factory.binTa5d6ff"Ta5d6ff,
Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6ffdef456Ta5d6ff"Ta5d6ff,
Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff: 4194304,
Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6fffactoryTa5d6ff"
Ta5d6ff }
]
}
Ta5d6ff"""
Tb4b4b4.Te6edf3trimIndentTb4b4b4(Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// ESP32 manifest-first resolution
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware uses manifest when available`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// Manifest is available
Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.mt.jsonTa5d6ff"Tff7b72] Tff7b72= Te6edf3MANIFEST_JSON

T8b949e// Direct download of the manifest-resolved filename succeeds
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve firmware via manifestTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware accepts manifest artifact when md5 and size match`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// Distinctive app0 name no fallback heuristic would construct, so resolving it proves the verified manifest
T8b949e// path returned the artifact (rather than a filename-heuristic fallback that happens to share the name).
Tff7b72val Te6edf3name Tff7b72= Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17-app0.binTa5d6ff"
Tff7b72val Te6edf3payload Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff2T79c0ff0T79c0ff4T79c0ff8Tb4b4b4) Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3toByteTb4b4b4(Tb4b4b4) Tb4b4b4}
Tff7b72val Te6edf3md5 Tff7b72= Te6edf3FirmwareHashUtilTb4b4b4.Te6edf3calculateMd5HexTb4b4b4(Te6edf3payloadTb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.mt.jsonTa5d6ff"Tff7b72] Tff7b72=
Ta5d6ff"""Ta5d6ff{Ta5d6ff"Ta5d6fffilesTa5d6ff"Ta5d6ff:[{Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff:Ta5d6ff"Tffd700$Te6edf3nameTa5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ffapp0Ta5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff:Ta5d6ff"Tffd700$Te6edf3md5Ta5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff:Tffd700${Te6edf3payloadTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff}]}Ta5d6ff"""
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/Tffd700$Te6edf3nameTa5d6ff"Tb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3fileBytesTff7b72[Te6edf3nameTff7b72] Tff7b72= Te6edf3payload

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffManifest artifact passing the md5+size check should resolveTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Te6edf3nameTb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware rejects manifest artifact on md5 mismatch and falls back`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

Tff7b72val Te6edf3name Tff7b72= Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17-app0.binTa5d6ff"
T8b949e// Size matches (4) so md5 is computed; the manifest md5 is wrong, so the artifact must be rejected.
Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.mt.jsonTa5d6ff"Tff7b72] Tff7b72=
Ta5d6ff"""Ta5d6ff{Ta5d6ff"Ta5d6fffilesTa5d6ff"Ta5d6ff:[{Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff:Ta5d6ff"Tffd700$Te6edf3nameTa5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ffapp0Ta5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ffdeadbeefTa5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff:4}]}Ta5d6ff"""
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/Tffd700$Te6edf3nameTa5d6ff"Tb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3fileBytesTff7b72[Te6edf3nameTff7b72] Tff7b72= Te6edf3byteArrayOfTb4b4b4(T79c0ff0Te6edf3x01Tb4b4b4, T79c0ff0Te6edf3x02Tb4b4b4, T79c0ff0Te6edf3x03Tb4b4b4, T79c0ff0Te6edf3x04Tb4b4b4)
T8b949e// Heuristic fallback the rejection should land on.
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware rejects manifest artifact on size mismatch and falls back`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

Tff7b72val Te6edf3name Tff7b72= Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17-app0.binTa5d6ff"
T8b949e// Blank md5 โ†’ only size is checked; the declared size disagrees with the download, so it must be rejected.
Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.mt.jsonTa5d6ff"Tff7b72] Tff7b72=
Ta5d6ff"""Ta5d6ff{Ta5d6ff"Ta5d6fffilesTa5d6ff"Ta5d6ff:[{Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff:Ta5d6ff"Tffd700$Te6edf3nameTa5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ffapp0Ta5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff:9999}]}Ta5d6ff"""
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/Tffd700$Te6edf3nameTa5d6ff"Tb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3fileBytesTff7b72[Te6edf3nameTff7b72] Tff7b72= Te6edf3ByteArrayTb4b4b4(T79c0ff1T79c0ff0Tb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware falls back to current naming when manifest unavailable`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// No manifest
T8b949e// Current naming direct download succeeds
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve firmware via current naming fallbackTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware resolves update bin when no manifest and no plain bin`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// No manifest, no plain .bin; only the bare app `-update.bin` is published.
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17-update.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve firmware via -update.binTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17-update.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware prefers update bin over plain bin when both exist`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// No manifest. Both files exist: on pre-2.7.17 releases the plain `.bin` is a *merged* bootloader+app image
T8b949e// (which esp_ota_end rejects when flashed to app0), while `-update.bin` is the bare app image. Confirmed on
T8b949e// hardware with 2.7.15: the merged image failed `OTA End`, the -update.bin app image is the OTA-able one.
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17-update.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17-update.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware falls back to zip extraction when all direct downloads fail`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// No manifest, no direct downloads succeed
T8b949e// Zip download succeeds and extraction finds a matching file
Te6edf3handlerTb4b4b4.Te6edf3zipDownloadResult Tff7b72=
Te6edf3FirmwareArtifactTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3CommonUriTb4b4b4.Te6edf3parseTb4b4b4(Ta5d6ff"Ta5d6fffile:///tmp/firmware_release.zipTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3fileName Tff7b72= Ta5d6ff"Ta5d6fffirmware_release.zipTa5d6ff"Tb4b4b4,
Te6edf3isTemporary Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Te6edf3handlerTb4b4b4.Te6edf3zipExtractionResult Tff7b72=
Te6edf3FirmwareArtifactTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3CommonUriTb4b4b4.Te6edf3parseTb4b4b4(Ta5d6ff"Ta5d6fffile:///tmp/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3fileName Tff7b72= Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4,
Te6edf3isTemporary Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve firmware via zip fallbackTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Te6edf3handlerTb4b4b4.Te6edf3downloadedUrlsTb4b4b4.Te6edf3any Tb4b4b4{ Tffa657itTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6fffirmware_release.zipTa5d6ff"Tb4b4b4) Tff7b72|Tff7b72| Tffa657itTb4b4b4.Te6edf3containsTb4b4b4(Ta5d6ff"Ta5d6ff.zipTa5d6ff"Tb4b4b4) Tb4b4b4}Tb4b4b4,
Ta5d6ff"Ta5d6ffShould have attempted zip downloadTa5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware returns null when the zip download throws`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// No manifest, no direct downloads; the zip fallback fails with a transient network error
Te6edf3handlerTb4b4b4.Te6edf3zipDownloadException Tff7b72= Te6edf3IllegalStateExceptionTb4b4b4(Ta5d6ff"Ta5d6ffconnection resetTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffA failed zip download must resolve to null, not propagateTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware returns null when all strategies fail`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// Everything fails โ€” no manifest, no direct downloads, no zip
Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould return null when all strategies failTa5d6ff"Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware skips manifest when JSON is malformed`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// Malformed manifest
Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.mt.jsonTa5d6ff"Tff7b72] Tff7b72= Ta5d6ff"Ta5d6ff{ not valid json }Ta5d6ff"

T8b949e// Current naming succeeds
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould fall through to current naming when manifest is malformedTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware skips manifest when no app0 entry`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

T8b949e// Manifest with no app0 entry
Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.mt.jsonTa5d6ff"Tff7b72] Tff7b72=
Ta5d6ff"""Ta5d6ff{Ta5d6ff"Ta5d6fffilesTa5d6ff"Ta5d6ff: [{Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6ffbootloader.binTa5d6ff"Ta5d6ff, Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6ffabcTa5d6ff"Ta5d6ff, Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff: 1024, Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff: Ta5d6ff"Ta5d6ffbootloaderTa5d6ff"Ta5d6ff}]}Ta5d6ff"""

T8b949e// Current naming succeeds
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould fall through when manifest has no app0 entryTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware strips v prefix from version for URLs`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

T8b949e// The manifest URL should use "2.7.17" not "v2.7.17"
Tff7b72val Te6edf3manifestFetchUrl Tff7b72= Te6edf3handlerTb4b4b4.Te6edf3fetchedTextUrlsTb4b4b4.Te6edf3firstOrNullTb4b4b4(Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3manifestFetchUrl Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3assertTrueTb4b4b4(Ta5d6ff"Ta5d6ffv2.7.17Ta5d6ff" Tff7b72!in Te6edf3manifestFetchUrlTb4b4b4, Ta5d6ff"Ta5d6ffManifest URL should not contain 'v' prefix: Tffd700$Te6edf3manifestFetchUrlTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// checkUrlExists calls should use bare version
Te6edf3handlerTb4b4b4.Te6edf3checkedUrlsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3url Tff7b72-Tff7b72>
Te6edf3assertTrueTb4b4b4(Ta5d6ff"Ta5d6fffirmware-v2.7.17Ta5d6ff" Tff7b72!in Te6edf3urlTb4b4b4, Ta5d6ff"Ta5d6ffURL should not contain 'v' prefix in firmware path: Tffd700$Te6edf3urlTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware uses platformioTarget over hwModelSlug`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-heltec-v3-2.7.17.binTa5d6ff"Tb4b4b4)

Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

T8b949e// All URLs should use "heltec-v3" (platformioTarget) not "HELTEC_V3" (hwModelSlug)
Tff7b72val Te6edf3allUrls Tff7b72= Te6edf3handlerTb4b4b4.Te6edf3checkedUrls Tff7b72+ Te6edf3handlerTb4b4b4.Te6edf3fetchedTextUrls Tff7b72+ Te6edf3handlerTb4b4b4.Te6edf3downloadedUrls
Te6edf3allUrlsTb4b4b4.Te6edf3forEach Tb4b4b4{ Te6edf3url Tff7b72-Tff7b72>
Te6edf3assertTrueTb4b4b4(Ta5d6ff"Ta5d6ffHELTEC_V3Ta5d6ff" Tff7b72!in Te6edf3urlTb4b4b4, Ta5d6ff"Ta5d6ffURL should use platformioTarget, not hwModelSlug: Tffd700$Te6edf3urlTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveEsp32Firmware uses hwModelSlug when platformioTarget is empty`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3hardware Tff7b72= Te6edf3TEST_HARDWARETb4b4b4.Te6edf3copyTb4b4b4(Te6edf3platformioTarget Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3hwModelSlug Tff7b72= Ta5d6ff"Ta5d6ffCUSTOM_BOARDTa5d6ff"Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.7.17/firmware-CUSTOM_BOARD-2.7.17.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3TEST_RELEASETb4b4b4, Te6edf3hardwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve using hwModelSlug fallbackTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-CUSTOM_BOARD-2.7.17.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Nightly channel (fixed firmware-nightly/ folder, no release zip)
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`nightly release resolves from the fixed firmware-nightly folder`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3nightly Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.8.0.f52e2eaTa5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3releaseType Tff7b72= Te6edf3FirmwareReleaseTypeTb4b4b4.Te6edf3NIGHTLYTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3textResponsesTff7b72[Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-nightly/firmware-heltec-v3-2.8.0.f52e2ea.mt.jsonTa5d6ff"Tff7b72] Tff7b72=
Ta5d6ff"""Ta5d6ff{Ta5d6ff"Ta5d6fffilesTa5d6ff"Ta5d6ff:[{Ta5d6ff"Ta5d6ffnameTa5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.8.0.f52e2ea.binTa5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffmd5Ta5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ff"Ta5d6ff,Ta5d6ff"Ta5d6ffbytesTa5d6ff"Ta5d6ff:0,Ta5d6ff"Ta5d6ffpart_nameTa5d6ff"Ta5d6ff:Ta5d6ff"Ta5d6ffapp0Ta5d6ff"Ta5d6ff}]}Ta5d6ff"""
Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-nightly/firmware-heltec-v3-2.8.0.f52e2ea.binTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3nightlyTb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffNightly should resolve from firmware-nightly/, not firmware-<version>/Ta5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-heltec-v3-2.8.0.f52e2ea.binTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3handlerTb4b4b4.Te6edf3checkedUrlsTb4b4b4.Te6edf3none Tb4b4b4{ Ta5d6ff"Ta5d6fffirmware-2.8.0.f52e2ea/Ta5d6ff" Tff7b72in Tffa657it Tb4b4b4}Tb4b4b4, Ta5d6ff"Ta5d6ffversioned folder must not be usedTa5d6ff"Tb4b4b4)
Te6edf3assertTrueTb4b4b4(
Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-nightly/firmware-heltec-v3-2.8.0.f52e2ea.mt.jsonTa5d6ff" Tff7b72in Te6edf3handlerTb4b4b4.Te6edf3fetchedTextUrlsTb4b4b4,
Ta5d6ff"Ta5d6ffmanifest must be fetched from firmware-nightly/Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`nightly ota zip resolves from the fixed firmware-nightly folder`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3hardware Tff7b72= Te6edf3DeviceHardwareTb4b4b4(Te6edf3hwModelSlug Tff7b72= Ta5d6ff"Ta5d6ffRAK4631Ta5d6ff"Tb4b4b4, Te6edf3platformioTarget Tff7b72= Ta5d6ff"Ta5d6ffrak4631Ta5d6ff"Tb4b4b4, Te6edf3architecture Tff7b72= Ta5d6ff"Ta5d6ffnrf52840Ta5d6ff"Tb4b4b4)
Tff7b72val Te6edf3nightly Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.8.0.f52e2eaTa5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3releaseType Tff7b72= Te6edf3FirmwareReleaseTypeTb4b4b4.Te6edf3NIGHTLYTb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-nightly/firmware-rak4631-2.8.0.f52e2ea-ota.zipTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveOtaFirmwareTb4b4b4(Te6edf3nightlyTb4b4b4, Te6edf3hardwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-rak4631-2.8.0.f52e2ea-ota.zipTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`nightly release without zip skips the zip fallback entirely`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3nightly Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.8.0.f52e2eaTa5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ff"Tb4b4b4, Te6edf3releaseType Tff7b72= Te6edf3FirmwareReleaseTypeTb4b4b4.Te6edf3NIGHTLYTb4b4b4)

T8b949e// Nothing published โ€” every strategy fails.
Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveEsp32FirmwareTb4b4b4(Te6edf3nightlyTb4b4b4, Te6edf3TEST_HARDWARETb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNullTb4b4b4(Te6edf3resultTb4b4b4)
Te6edf3assertTrueTb4b4b4(Te6edf3handlerTb4b4b4.Te6edf3downloadedUrlsTb4b4b4.Te6edf3isEmptyTb4b4b4(Tb4b4b4)Tb4b4b4, Ta5d6ff"Ta5d6ffno zip download may be attempted when zipUrl is blankTa5d6ff"Tb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// OTA firmware (nRF52 DFU zip)
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveOtaFirmware constructs correct filename for nRF52`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3hardware Tff7b72= Te6edf3DeviceHardwareTb4b4b4(Te6edf3hwModelSlug Tff7b72= Ta5d6ff"Ta5d6ffRAK4631Ta5d6ff"Tb4b4b4, Te6edf3platformioTarget Tff7b72= Ta5d6ff"Ta5d6ffrak4631Ta5d6ff"Tb4b4b4, Te6edf3architecture Tff7b72= Ta5d6ff"Ta5d6ffnrf52840Ta5d6ff"Tb4b4b4)
Tff7b72val Te6edf3release Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.5.0Ta5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ffhttps://example.com/nrf52.zipTa5d6ff"Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.5.0/firmware-rak4631-2.5.0-ota.zipTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveOtaFirmwareTb4b4b4(Te6edf3releaseTb4b4b4, Te6edf3hardwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve OTA firmware for nRF52Ta5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-rak4631-2.5.0-ota.zipTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveOtaFirmware uses platformioTarget for variant`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3hardware Tff7b72=
Te6edf3DeviceHardwareTb4b4b4(
Te6edf3hwModelSlug Tff7b72= Ta5d6ff"Ta5d6ffRAK4631Ta5d6ff"Tb4b4b4,
Te6edf3platformioTarget Tff7b72= Ta5d6ff"Ta5d6ffrak4631_nomadstar_meteor_proTa5d6ff"Tb4b4b4,
Te6edf3architecture Tff7b72= Ta5d6ff"Ta5d6ffnrf52840Ta5d6ff"Tb4b4b4,
Tb4b4b4)
Tff7b72val Te6edf3release Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.5.0Ta5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ffhttps://example.com/nrf52.zipTa5d6ff"Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.5.0/firmware-rak4631_nomadstar_meteor_pro-2.5.0-ota.zipTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveOtaFirmwareTb4b4b4(Te6edf3releaseTb4b4b4, Te6edf3hardwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve OTA firmware for nRF52 variantTa5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-rak4631_nomadstar_meteor_pro-2.5.0-ota.zipTa5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// USB firmware
T8b949e// -----------------------------------------------------------------------

Tf0883e@Test
Tff7b72fun Td2a8ff`retrieveUsbFirmware constructs correct filename for RP2040`Tb4b4b4(Tb4b4b4) Tff7b72= Te6edf3runTest Tb4b4b4{
Tff7b72val Te6edf3handler Tff7b72= Te6edf3FakeFirmwareFileHandlerTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3retriever Tff7b72= Te6edf3FirmwareRetrieverTb4b4b4(Te6edf3handlerTb4b4b4)
Tff7b72val Te6edf3hardware Tff7b72= Te6edf3DeviceHardwareTb4b4b4(Te6edf3hwModelSlug Tff7b72= Ta5d6ff"Ta5d6ffRPI_PICOTa5d6ff"Tb4b4b4, Te6edf3platformioTarget Tff7b72= Ta5d6ff"Ta5d6ffpicoTa5d6ff"Tb4b4b4, Te6edf3architecture Tff7b72= Ta5d6ff"Ta5d6ffrp2040Ta5d6ff"Tb4b4b4)
Tff7b72val Te6edf3release Tff7b72= Te6edf3FirmwareReleaseTb4b4b4(Te6edf3id Tff7b72= Ta5d6ff"Ta5d6ffv2.5.0Ta5d6ff"Tb4b4b4, Te6edf3zipUrl Tff7b72= Ta5d6ff"Ta5d6ffhttps://example.com/rp2040.zipTa5d6ff"Tb4b4b4)

Te6edf3handlerTb4b4b4.Te6edf3existingUrlsTb4b4b4.Te6edf3addTb4b4b4(Ta5d6ff"Tffd700$Te6edf3BASE_URLTa5d6ff/firmware-2.5.0/firmware-pico-2.5.0.uf2Ta5d6ff"Tb4b4b4)

Tff7b72val Te6edf3result Tff7b72= Te6edf3retrieverTb4b4b4.Te6edf3retrieveUsbFirmwareTb4b4b4(Te6edf3releaseTb4b4b4, Te6edf3hardwareTb4b4b4) Tb4b4b4{Tb4b4b4}

Te6edf3assertNotNullTb4b4b4(Te6edf3resultTb4b4b4, Ta5d6ff"Ta5d6ffShould resolve USB firmware for RP2040Ta5d6ff"Tb4b4b4)
Te6edf3assertEqualsTb4b4b4(Ta5d6ff"Ta5d6fffirmware-pico-2.5.0.uf2Ta5d6ff"Tb4b4b4, Te6edf3resultTb4b4b4.Te6edf3fileNameTb4b4b4)
Tb4b4b4}

T8b949e// -----------------------------------------------------------------------
T8b949e// Test infrastructure
T8b949e// -----------------------------------------------------------------------

T8b949e/**
* A fake [FirmwareFileHandler] for testing [FirmwareRetriever] without network or filesystem.
*
* Configure behavior by populating:
* - [existingUrls] โ€” URLs that [checkUrlExists] returns true for
* - [textResponses] โ€” URL โ†’ text body for [fetchText]
* - [zipDownloadResult] / [zipExtractionResult] โ€” for zip fallback path
*/
Tff7b72protected Tff7b72class T56d364FakeFirmwareFileHandler Tb4b4b4: Te6edf3FirmwareFileHandler Tb4b4b4{
T8b949e/** URLs that [checkUrlExists] will return true for. */
Tff7b72val Te6edf3existingUrls Tff7b72= Te6edf3mutableSetOfTff7b72<Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)

T8b949e/** URL โ†’ text body for [fetchText]. */
Tff7b72val Te6edf3textResponses Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657StringTb4b4b4, Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)

T8b949e/** Result returned by [downloadFile] when the filename is "firmware_release.zip". */
Tff7b72var Te6edf3zipDownloadResultTb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Tff7b72null

T8b949e/** When set, [downloadFile] throws this for the "firmware_release.zip" download instead of returning. */
Tff7b72var Te6edf3zipDownloadExceptionTb4b4b4: Te6edf3Exception? Tff7b72= Tff7b72null

T8b949e/** Result returned by [extractFirmwareFromZip]. */
Tff7b72var Te6edf3zipExtractionResultTb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Tff7b72null

T8b949e/** Bytes (and thus size) reported by [readBytes] / [getFileSize] for a downloaded file, keyed by fileName. */
Tff7b72val Te6edf3fileBytes Tff7b72= Te6edf3mutableMapOfTff7b72<Tffa657StringTb4b4b4, Te6edf3ByteArrayTff7b72>Tb4b4b4(Tb4b4b4)

T8b949e// Tracking
Tff7b72val Te6edf3checkedUrls Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3fetchedTextUrls Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3downloadedUrls Tff7b72= Te6edf3mutableListOfTff7b72<Tffa657StringTff7b72>Tb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72fun Td2a8ffcleanupAllTemporaryFilesTb4b4b4(Tb4b4b4) Tb4b4b4{Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcheckUrlExistsTb4b4b4(Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657Boolean Tb4b4b4{
Te6edf3checkedUrlsTb4b4b4.Te6edf3addTb4b4b4(Te6edf3urlTb4b4b4)
Tff7b72return Te6edf3url Tff7b72in Te6edf3existingUrls
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8fffetchTextTb4b4b4(Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4)Tb4b4b4: Tffa657String? Tb4b4b4{
Te6edf3fetchedTextUrlsTb4b4b4.Te6edf3addTb4b4b4(Te6edf3urlTb4b4b4)
Tff7b72return Te6edf3textResponsesTff7b72[Te6edf3urlTff7b72]
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffdownloadFileTb4b4b4(
Te6edf3urlTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3fileNameTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3onProgressTb4b4b4: Tb4b4b4(Tffa657FloatTb4b4b4) Tff7b72-Tff7b72> Tffa657UnitTb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tb4b4b4{
Te6edf3downloadedUrlsTb4b4b4.Te6edf3addTb4b4b4(Te6edf3urlTb4b4b4)
Te6edf3onProgressTb4b4b4(T79c0ff1fTb4b4b4)

T8b949e// Zip download path
Tff7b72if Tb4b4b4(Te6edf3fileName Tff7b72=Tff7b72= Ta5d6ff"Ta5d6fffirmware_release.zipTa5d6ff"Tb4b4b4) Tb4b4b4{
Te6edf3zipDownloadExceptionTff7b72?.Te6edf3let Tb4b4b4{ Tff7b72throw Tffa657it Tb4b4b4}
Tff7b72return Te6edf3zipDownloadResult
Tb4b4b4}

T8b949e// Direct download: only succeed if the URL was registered as existing
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3url Tff7b72in Te6edf3existingUrlsTb4b4b4) Tb4b4b4{
Te6edf3FirmwareArtifactTb4b4b4(
Te6edf3uri Tff7b72= Te6edf3CommonUriTb4b4b4.Te6edf3parseTb4b4b4(Ta5d6ff"Ta5d6fffile:///tmp/Tffd700$Te6edf3fileNameTa5d6ff"Tb4b4b4)Tb4b4b4,
Te6edf3fileName Tff7b72= Te6edf3fileNameTb4b4b4,
Te6edf3isTemporary Tff7b72= Tff7b72trueTb4b4b4,
Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72null
Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffextractFirmwareTb4b4b4(
Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4,
Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4,
Te6edf3fileExtensionTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3preferredFilenameTb4b4b4: Tffa657String?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Tff7b72null

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffextractFirmwareFromZipTb4b4b4(
Te6edf3zipFileTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4,
Te6edf3hardwareTb4b4b4: Te6edf3DeviceHardwareTb4b4b4,
Te6edf3fileExtensionTb4b4b4: Tffa657StringTb4b4b4,
Te6edf3preferredFilenameTb4b4b4: Tffa657String?Tb4b4b4,
Tb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Te6edf3zipExtractionResult

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetFileSizeTb4b4b4(Te6edf3fileTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Tffa657Long Tff7b72= Tb4b4b4(Te6edf3fileBytesTff7b72[Te6edf3fileTb4b4b4.Te6edf3fileNameTff7b72]Tff7b72?.Te6edf3size Tff7b72?: T79c0ff0Tb4b4b4)Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffreadBytesTb4b4b4(Te6edf3artifactTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tff7b72=
Te6edf3fileBytesTff7b72[Te6edf3artifactTb4b4b4.Te6edf3fileNameTff7b72] Tff7b72?: Te6edf3ByteArrayTb4b4b4(T79c0ff0Tb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffimportFromUriTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4)Tb4b4b4: Te6edf3FirmwareArtifact? Tff7b72= Tff7b72null

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffgetDisplayNameTb4b4b4(Te6edf3uriTb4b4b4: Te6edf3CommonUriTb4b4b4)Tb4b4b4: Tffa657String? Tff7b72= Te6edf3uriTb4b4b4.Te6edf3pathSegmentsTb4b4b4.Te6edf3lastOrNullTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffextractZipEntriesTb4b4b4(Te6edf3artifactTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4)Tb4b4b4: Te6edf3MapTff7b72<Tffa657StringTb4b4b4, Te6edf3ByteArrayTff7b72> Tff7b72= Te6edf3emptyMapTb4b4b4(Tb4b4b4)

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffdeleteFileTb4b4b4(Te6edf3fileTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4) Tb4b4b4{Tb4b4b4}

Tff7b72override Tff7b72suspend Tff7b72fun Td2a8ffcopyToUriTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3FirmwareArtifactTb4b4b4, Te6edf3destinationUriTb4b4b4: Te6edf3CommonUriTb4b4b4)Tb4b4b4: Tffa657Long Tff7b72= T79c0ff0L
Tb4b4b4}
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.13s